#include "Pch.h"
#include "Dx11Window.h"
#include "Dx11Globals.h"

#include "../../../MCD/Core/System/Log.h"
#include "../../../MCD/Core/System/Window.inl"
#include "../../../MCD/Core/System/Window.Win.inl"
#include "../../../MCD/Core/System/StrUtility.h"
#include "../../../MCD/Core/System/MemoryProfiler.h"

namespace MCD {

class Dx11Window::Impl : public Window::Impl
{
public:
	typedef Window::Impl Super;

	Impl(Window& w)
		: Super(w)
		, mDXGISwapChain(nullptr)
		, mD3D11RenderTargetView(nullptr)
		, mDepthStencilTexture(nullptr)
		, mDepthStencilView(nullptr)
		, mRasterizerState(nullptr)
		, mSyncInterval(0)
		, mMultiSampleLevel(1)
		, mMultiSampleQty(0)
	{
	}

	sal_override ~Impl()
	{
	}

	sal_override void createWindow(Window::Handle existingWindowHandle=0)
	{
		MemoryProfiler::Scope profiler("Dx11Window::createWindow");
		Super::createWindow(existingWindowHandle);

		/// Concepts on SwapChain & Device references
		// http://msdn.microsoft.com/en-us/library/ee415671%28VS.85%29.aspx
		std::vector<D3D_FEATURE_LEVEL> requestLvls;
		//requestLvls.push_back(D3D_FEATURE_LEVEL_9_3);
		requestLvls.push_back(D3D_FEATURE_LEVEL_10_0);

		HRESULT hr;

		DWORD flags = 0;
		
#if defined(DEBUD) || defined(_DEBUG)
		// allow extended debug message in DEBUG mode
		flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

		// initialize d3d device
		if(nullptr == getD3D11Device())
		{
			// d3d11 device is nullptr, we need to create one
			hr = D3D11CreateDevice(
				nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
				flags, &requestLvls[0], requestLvls.size(), D3D11_SDK_VERSION,
				&getD3D11Device(), nullptr, &getD3D11DeviceContextIM()
				);

			if(FAILED(hr))
			{
				Log::format(Log::Error, L"Dx11Window: failed to create hardware D3D11Device, try WARP...");

				hr = D3D11CreateDevice(
					nullptr, D3D_DRIVER_TYPE_WARP, nullptr,
					flags, &requestLvls[0], requestLvls.size(), D3D11_SDK_VERSION,
					&getD3D11Device(), nullptr, &getD3D11DeviceContextIM()
					);

				if(FAILED(hr))
				{
					Log::format(Log::Error, L"Dx11Window: failed to create WARP D3D11Device!");
				}
			}
			
			//if(flags & D3D11_CREATE_DEVICE_DEBUG)
			//{
			//	ID3D11Debug* dbg = nullptr;
			//	hr = getD3D11Device()->QueryInterface(IID_ID3D11Debug, reinterpret_cast<void**>(&dbg));
			//	if(SUCCEEDED(hr))
			//	{
			//		dbg->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
			//	}
			//}
		}
		else
		{
			getD3D11Device()->AddRef();
			getD3D11DeviceContextIM()->AddRef();
		}
		
		createSwapChain();
		createRenderTargetView();
		createDepthStencilBufferAndView();

	}

	void createSwapChain()
	{
		HRESULT hr;

		// get the IDXGIFactory1
		IDXGIDevice* dxgiDevice;
		IDXGIAdapter* dxgiAdapter;
		IDXGIFactory1* dxgiFactory;
		getD3D11Device()->QueryInterface(&dxgiDevice);
		dxgiDevice->GetAdapter(&dxgiAdapter);
		dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));

		DXGI_SWAP_CHAIN_DESC sd;
		ZeroMemory(&sd, sizeof(sd));
		sd.BufferCount = 1;
		sd.BufferDesc.Width = mWidth;
		sd.BufferDesc.Height = mHeight;
		sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		sd.BufferDesc.RefreshRate.Numerator = 60;
		sd.BufferDesc.RefreshRate.Denominator = 1;
		sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		sd.OutputWindow = mWnd;
		sd.SampleDesc.Count = mMultiSampleLevel;
		sd.SampleDesc.Quality = mMultiSampleQty;
		sd.Windowed = TRUE;

		hr = dxgiFactory->CreateSwapChain(getD3D11Device(), &sd, &mDXGISwapChain);
		safeRelease(dxgiFactory);
		safeRelease(dxgiAdapter);
		safeRelease(dxgiDevice);

		if(FAILED(hr))
			Log::format(Log::Error, L"Dx11Window: failed to create DXGISwapChain!");

		if(mFullScreen)
			mDXGISwapChain->SetFullscreenState((BOOL)mFullScreen, nullptr);

		// default RasterizerState according to the MSAA option
		D3D11_RASTERIZER_DESC rd;
		rd.FillMode = D3D11_FILL_SOLID;
		rd.CullMode = D3D11_CULL_BACK;
		rd.FrontCounterClockwise = FALSE;
		rd.DepthBias = 0;
		rd.DepthBiasClamp = 0.0f;
		rd.SlopeScaledDepthBias = 0.0f;
		rd.DepthClipEnable = TRUE;
		rd.ScissorEnable = FALSE;
		rd.AntialiasedLineEnable = FALSE;
		rd.MultisampleEnable = mMultiSampleLevel > 1 ? TRUE : FALSE;

		getD3D11Device()->CreateRasterizerState(&rd, &mRasterizerState);

		if(FAILED(hr))
			Log::format(Log::Error, L"Dx11Window: failed to create default RasterizerState!");
	}

	void createRenderTargetView()
	{
		HRESULT hr;

		ID3D11Resource* backBuffer;
		mDXGISwapChain->GetBuffer(0, __uuidof(backBuffer), reinterpret_cast<void**>(&backBuffer));

		D3D11_RENDER_TARGET_VIEW_DESC rtd;
		rtd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		
		if(1 == mMultiSampleLevel)
		{
			rtd.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
			rtd.Texture2D.MipSlice = 0;
		}
		else
		{
			rtd.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS; 
		}

		hr = getD3D11Device()->CreateRenderTargetView(backBuffer, &rtd, &mD3D11RenderTargetView);

		safeRelease(backBuffer);

		if(FAILED(hr))
			Log::format(Log::Error, L"Dx11Window: failed to create RenderTargetView for swapchain!");
	}

	void createDepthStencilBufferAndView()
	{
		HRESULT hr;

		// reference: http://www.dx11.org.uk/3dcube.htm
		D3D11_TEXTURE2D_DESC desc;
		desc.Width = mWidth;
		desc.Height = mHeight;
		desc.MipLevels = 1;
		desc.ArraySize = 1;
		desc.Format = DXGI_FORMAT_D32_FLOAT;
		desc.SampleDesc.Count = mMultiSampleLevel;
		desc.SampleDesc.Quality = mMultiSampleQty;
		desc.Usage = D3D11_USAGE_DEFAULT;
		desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
		desc.CPUAccessFlags = 0;
		desc.MiscFlags = 0;

		// depth stencil texture
		hr = getD3D11Device()->CreateTexture2D(&desc, NULL, &mDepthStencilTexture);

		if(FAILED(hr))
			Log::format(Log::Error, L"Dx11Window: failed to create Depth-Stencil texture!");
		
		// depth stencil view
		hr = getD3D11Device()->CreateDepthStencilView(mDepthStencilTexture, NULL, &mDepthStencilView);

		if(FAILED(hr))
			Log::format(Log::Error, L"Dx11Window: failed to create Depth-Stencil view!");
	}

	sal_override void destroy()
	{
		if(nullptr == mDXGISwapChain)
			return; // all resources has already been destroyed, just return

		safeRelease(mDepthStencilView);
		safeRelease(mDepthStencilTexture);
		safeRelease(mD3D11RenderTargetView);
		safeRelease(mRasterizerState);

		// check for full screen state and change back to windowed mode before releasing mDXGISwapChain
		if(mFullScreen)
			mDXGISwapChain->SetFullscreenState(FALSE, nullptr);

		safeRelease(mDXGISwapChain);

		getD3D11DeviceContextIM()->Release();
		getD3D11Device()->Release();
		
		Super::destroy();
	}

	bool makeActive()
	{
		MCD_ASSUME(nullptr != mD3D11RenderTargetView);
		MCD_ASSUME(nullptr != getD3D11DeviceContextIM());
		
		getD3D11DeviceContextIM()->OMSetRenderTargets(1, &mD3D11RenderTargetView, mDepthStencilView);
		D3D11_VIEWPORT vp;
		vp.TopLeftX = 0.0f;
		vp.TopLeftY = 0.0f;
		vp.Width = (float)mWidth;
		vp.Height = (float)mHeight;
		vp.MinDepth = 0.0f;
		vp.MaxDepth = 1.0f;

		getD3D11DeviceContextIM()->RSSetViewports(1, &vp);
		getD3D11DeviceContextIM()->RSSetState(mRasterizerState);
		
		return true;
	}

	void clearColorBuffer(const float* colorValues)
	{
		getD3D11DeviceContextIM()->ClearRenderTargetView(mD3D11RenderTargetView, colorValues);
	}

	void clearDepthStencilBuffer(float depthValue, uint8_t stencilValue)
	{
		getD3D11DeviceContextIM()->ClearDepthStencilView(
			mDepthStencilView,
			D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL,
			depthValue,
			stencilValue
			);
	}

	bool swapBuffers()
	{
		return SUCCEEDED(mDXGISwapChain->Present(mSyncInterval, 0));
	}

	bool setVerticalSync(bool flag)
	{
		mSyncInterval = (true == flag) ? 1:0;

		return true;
	}

	sal_override void setOption(const wchar_t* name, const wchar_t* value)
	{
		bool recreateSwapChain = false;

		if(::wcscmp(name, L"MSAA") == 0)
		{
			const int lastValue = mMultiSampleLevel;
			mMultiSampleLevel = wStr2IntWithDefault(value, 1);
			recreateSwapChain = lastValue != mMultiSampleLevel;
		}
		else if(::wcscmp(name, L"MSAAQty") == 0)
		{
			const int lastValue = mMultiSampleQty;
			mMultiSampleQty = wStr2IntWithDefault(value, 0);
			recreateSwapChain = lastValue != mMultiSampleQty;
		}
		else
		{
			// default handler
			Super::setOption(name, value);
		}

		if(recreateSwapChain)
		{
			Log::format(Log::Info, L"Dx11Window: recreating SwapChain");
			safeRelease(mD3D11RenderTargetView);
			safeRelease(mDepthStencilTexture);
			safeRelease(mDepthStencilView);
			safeRelease(mRasterizerState);
			safeRelease(mDXGISwapChain);

			createSwapChain();
			createRenderTargetView();
			createDepthStencilBufferAndView();

			Log::format(Log::Info, L"Dx11Window: recreate SwapChain done");
		}
	}

	void handleResize()
	{
		//Log::format(Log::Info, L"Dx11Window resize %d %d", mWidth, mHeight);
		if(nullptr == mDXGISwapChain)
			return;

		if(mWidth <= 1 || mHeight <= 1)
			return;

		HRESULT hr;

		// unbound and release the buffer related objects before resize
		getD3D11DeviceContextIM()->OMSetRenderTargets(0, nullptr, nullptr);
		
		safeRelease(mD3D11RenderTargetView);
		safeRelease(mDepthStencilTexture);
		safeRelease(mDepthStencilView);

		hr = mDXGISwapChain->ResizeBuffers(1, mWidth, mHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0);

		if(FAILED(hr))
			Log::format(Log::Info, L"Dx11Window: failed to ResizeBuffers()!");

		createRenderTargetView();
		createDepthStencilBufferAndView();
	}

private:
	IDXGISwapChain* mDXGISwapChain;
	ID3D11RenderTargetView* mD3D11RenderTargetView;
	ID3D11Texture2D* mDepthStencilTexture;
	ID3D11DepthStencilView* mDepthStencilView;
	ID3D11RasterizerState* mRasterizerState;
	int mSyncInterval;
	int mMultiSampleLevel;
	int mMultiSampleQty;
	
};	// Impl

Dx11Window::Dx11Window()
{
}

Dx11Window::~Dx11Window()
{
	destroy();
}

void Dx11Window::create(const wchar_t* options) throw(std::exception)
{
	create(0, options);
}

void Dx11Window::create(Handle existingControl, const wchar_t* options) throw(std::exception)
{
	if(!mImpl)
		mImpl = new Impl(*this);

	// Not that we should operate on the local Impl class but not Window::Impl class.
	Impl* impl = static_cast<Impl*>(mImpl);
	MCD_ASSUME(impl != nullptr);

//#ifdef MCD_VC
//	{	// Create a dummy window first
//		// See http://www.gamedev.net/community/forums/topic.asp?topic_id=423903 about a discussion
//		// on using a dummy window.
//		int showBackup = impl->mShowWindow;
//		impl->mShowWindow = false;
//		impl->createWindow();
//		if(options)
//			impl->setOptions(options);
//		//if(!impl->detectMultiSamplePixelFormat())
//		//	Log::format(Log::Warn, L"The requested level of full-screen anti-aliasing is not supported.");
//		impl->destroy();
//		impl->mShowWindow = showBackup;
//	}
//#endif

	if(options)
		impl->setOptions(options);

	impl->createWindow(existingControl);
}

void Dx11Window::destroy()
{
	MCD_ASSUME(mImpl != nullptr);
	static_cast<Impl*>(mImpl)->destroy();
}

bool Dx11Window::makeActive()
{
	MCD_ASSUME(mImpl != nullptr);
	return static_cast<Impl*>(mImpl)->makeActive();
}

void Dx11Window::clearColorBuffer(const float* colorValues)
{
	MCD_ASSUME(mImpl != nullptr);
	return static_cast<Impl*>(mImpl)->clearColorBuffer(colorValues);
}

void Dx11Window::clearDepthStencilBuffer(float depthValue, uint8_t stencilValue)
{
	MCD_ASSUME(mImpl != nullptr);
	return static_cast<Impl*>(mImpl)->clearDepthStencilBuffer(depthValue, stencilValue);
}

bool Dx11Window::swapBuffers()
{
	MemoryProfiler::Scope profiler("Dx11Window::swapBuffers");

	MCD_ASSUME(mImpl != nullptr);
	return static_cast<Impl*>(mImpl)->swapBuffers();
}

bool Dx11Window::setVerticalSync(bool flag)
{
	MCD_ASSUME(mImpl != nullptr);
	return static_cast<Impl*>(mImpl)->setVerticalSync(flag);
}

void Dx11Window::onEvent(const Event& eventReceived)
{
	MCD_ASSUME(mImpl != nullptr);

	if(eventReceived.Type == Event::Resized)
		static_cast<Impl*>(mImpl)->handleResize();

	// Let Window (the super class of Dx11Window) to do the remaining job
	Window::onEvent(eventReceived);
}

}	// namespace MCD